
#include "Type.h"
#include "Object.h"

namespace mjc {

	Type::Type(const Kind& _kind)
		:kind(_kind),
		arrayData(NULL),
		classData(NULL)
	{
		switch(kind)
		{
		case K_ARRAY:
			arrayData = new ArrayData;
			break;

		case K_CLASS:
			classData = new ClassData;
		}
	}

	Type::Type(const ArrayData& ad)
		:kind(K_ARRAY),
		arrayData(new ArrayData(ad)),
		classData(NULL)
	{}

	Type::Type(const ClassData& cd)
		:kind(K_CLASS),
		arrayData(NULL),
		classData(new ClassData(cd))
	{}

	Type::Type(const Type& other)
		:kind(other.kind),
		arrayData(NULL),
		classData(NULL)
	{
		switch(kind)
		{
		case K_ARRAY:
			arrayData = new ArrayData(*other.arrayData);
			break;

		case K_CLASS:
			classData = new ClassData;
			SetClassData(*other.classData);
		}
	}

	Type::~Type()
	{
		delete arrayData; arrayData = NULL;
		delete classData; classData = NULL;
	}
	
	Type& Type::operator = (const Type& other)
	{
		kind = other.kind;

		switch(kind)
		{
		case K_ARRAY:
			if(arrayData == NULL)
				arrayData = new ArrayData(*other.arrayData);
			else
				*arrayData = *other.arrayData;
			delete classData;
			classData = NULL;
			break;

		case K_CLASS:
			if(classData == NULL)
				classData = new ClassData(*other.classData);
			else
				*classData = *other.classData;
			delete arrayData;
			arrayData = NULL;
		}

		return *this;
	}

	bool Type::operator == (const Type& other) const
	{
		if(kind == K_ARRAY)
			return other.kind == K_ARRAY && 
					(other.arrayData->GetType() == arrayData->GetType() ||
					other.arrayData->GetType().kind == K_NONE);
		
		return kind == other.kind;
	}

	bool Type::operator != (const Type& other) const
	{
		return !(*this == other);
	}
	
	bool Type::IsReferenceType() const
	{
		return (kind == K_ARRAY || kind == K_CLASS);
	}

	bool Type::Compatible(const Type& other) const
	{
		return (*this == other) || 
			   (kind == K_NONE && other.IsReferenceType()) || 
			   (this->IsReferenceType() && other.kind == K_NONE);
	}

	bool Type::Assignable(const Type& other) const
	{
		return (*this == other) || 
			   (kind == K_NONE && other.IsReferenceType());
	}

	Type::ArrayData& Type::GetArrayData() 
	{
		assert(kind == K_ARRAY);
		return *arrayData;
	}
	
	Type::ArrayData Type::GetArrayData() const
	{
		assert(kind == K_ARRAY);
		return *arrayData;
	}

	void Type::SetArrayData(const Type::ArrayData& ad)
	{
		assert(kind == K_ARRAY);
		*arrayData = ad;
	}

	Type::ClassData& Type::GetClassData()
	{
		assert(kind == K_CLASS);
		return *classData;
	}

	Type::ClassData Type::GetClassData() const
	{
		assert(kind == K_CLASS);
		return *classData;
	}

	void Type::SetClassData(const Type::ClassData& cd)
	{
		assert(kind == K_CLASS);
		*classData = cd;
	}

	//ArrayData
	Type::ArrayData::ArrayData()
		:type(new Type(K_NONE))
	{}

	Type::ArrayData::ArrayData(const Type::ArrayData& ad)
		:type(new Type(*ad.type))
	{}

	Type::ArrayData::ArrayData(const Type& _type)
		:type(new Type(_type))
	{}

	Type::ArrayData::~ArrayData()
	{
		delete type;
	}

	Type::ArrayData& Type::ArrayData::operator = (const Type::ArrayData& ad)
	{
		*type = *ad.type;
		return *this;
	}


	//ClassData
	Type::ClassData::ClassData()
		:fields()
	{}

	Type::ClassData::ClassData(const Type::ClassData& cd)
		:fields()
	{
		for(unsigned int i=0; i<cd.fields.size(); ++i)
			fields.push_back(new Object(*cd.fields[i]));
	}

	Type::ClassData::~ClassData()
	{
		for(unsigned int i=0; i<fields.size(); ++i)
			delete fields[i];

		fields.clear();
	}

	Type::ClassData& Type::ClassData::operator = (const Type::ClassData& cd)
	{
		for(unsigned int i=0; i<fields.size(); ++i)
			delete fields[i];
		fields.clear();

		for(unsigned int i=0; i<cd.fields.size(); ++i)
			fields.push_back(new Object(*cd.fields[i]));

		return *this;
	}
	
	void Type::ClassData::AddField(const Object& field)
	{
		fields.push_back(new Object(field));
	}

	Object* Type::ClassData::FindField(const std::wstring& name)
	{
		for(unsigned int i=0; i<fields.size(); ++i)
			if(fields[i]->GetName() == name)
				return fields[i];

		return NULL;
	}

	//for printing type information
	std::wostream& operator << (std::wostream& os, const Type& type)
	{
		switch(type.kind)
		{
		case Type::K_NONE:
			os << L"none";
			break;

		case Type::K_INT:
			os << L"int";
			break;

		case Type::K_CHAR:
			os << L"char";
			break;

		case Type::K_ARRAY:
			os << L"array (" << type.GetArrayData().GetType() << L")" ;
			break;

		case Type::K_CLASS:
			os << L"class";
			if(type.GetClassData().FieldCount() > 0)
			{
				os << L" <";

				for(unsigned int i=0; i<type.GetClassData().FieldCount(); ++i)
					os << L"(" << type.GetClassData().GetField(i) << L") ";

				os << L">";
			}
		}

		return os;
	}
}